package com.spark.gaea.mvc;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.spark.gaea.base.CmdVo;
import com.spark.gaea.core.CmdList;
import com.spark.gaea.mvc.annotation.MvcDescription;
import com.spark.gaea.mvc.annotation.MvcGroup;
import com.spark.gaea.utils.ClassUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.*;

public class MvcCmdList implements CmdList {
	private static final Logger LOG;
	private List<CmdVo> cmdList;
	@Autowired
	Environment env;
	private String misId;
	private String since;
	private String packages;

	public MvcCmdList() {
		this.cmdList = Lists.newArrayList();
		this.since = null;
	}

	@Override
	public String getMisId() {
		if (this.misId == null) {
			this.misId = this.env.getProperty("system.mis.id", String.class, "");
		}
		return this.misId;
	}

	@Override
	public String getPackage() {
		return this.packages;
	}

	@Override
	@PostConstruct
	public void init() {
		final String[] packages = this.getPackage().split(",");
		final List<Class<?>> classes = Lists.newArrayList();
		for (final String packag : packages) {
			classes.addAll(ClassUtil.getClasses(packag));
		}
		this.cmdList = this.getCmdVoList(classes);
	}

	@Override
	public List<CmdVo> getCommands() {
		if (this.cmdList == null) {
			this.init();
		}
		return this.cmdList;
	}

	public String getSince() {
		if (this.since == null) {
			this.since = this.env.getProperty("system.since", "0.1.0");
		}
		return this.since;
	}

	public void setMisId(final String misId) {
		this.misId = misId;
	}

	public void setSince(final String since) {
		this.since = since;
	}

	public String getPackages() {
		return this.packages;
	}

	public void setPackages(final String packages) {
		this.packages = packages;
	}

	public List<CmdVo> getCmdVoList(final List<Class<?>> classes) {
		final Map<String, String> groupMap = Maps.newHashMap();
		final Map<String, List<CmdVo>> extedsCmd = Maps.newHashMap();
		final Map<String, CmdVo> cmdMapping = Maps.newHashMap();
		for (final Class<?> aClass : classes) {
			this.proccessCmd(aClass, cmdMapping, groupMap, extedsCmd);
		}
		this.proccessExtend(cmdMapping, extedsCmd);
		final List<CmdVo> collection = (List<CmdVo>) Lists.newArrayList((Iterable) cmdMapping.values());
		for (final CmdVo cmd : collection) {
			if (!cmd.getIncludes().isEmpty()) {
				for (final String include : cmd.getIncludes()) {
					if (!cmdMapping.containsKey(include)) {
						MvcCmdList.LOG.warn(cmd.getName() + " Include \u4e0d\u5b58\u5728\u7684cmd\uff1a" + include);
					} else {
						final CmdVo vo = new CmdVo();
						vo.setName(cmd.getName() + "_" + include);
						vo.setGroup("0");
						vo.setGroupName(cmd.getName());
						vo.setDescription(this.getMisId() + ":" + include);
						vo.setSince(this.getSince());
						vo.setUsage("");
						vo.setType("include");
						vo.setMisId(this.getMisId());
						if (cmdMapping.containsKey(vo.getName())) {
							MvcCmdList.LOG.warn("\u542b\u6709\u91cd\u590d\u7684CmdGroup :" + vo.getName());
						}
						cmdMapping.put(vo.getName(), vo);
					}
				}
			}
			if (!cmd.getExcludes().isEmpty()) {
				for (final String exclude : cmd.getExcludes()) {
					if (!cmdMapping.containsKey(exclude)) {
						MvcCmdList.LOG.warn(cmd.getName() + " Exclude \u4e0d\u5b58\u5728\u7684cmd\uff1a" + exclude);
					}
				}
			}
		}
		for (final String groupName : groupMap.keySet()) {
			final CmdVo vo2 = new CmdVo();
			vo2.setName(groupName);
			vo2.setDescription(groupMap.get(groupName));
			vo2.setGroup("0");
			vo2.setGroupName("0");
			vo2.setSince("");
			vo2.setUsage("");
			vo2.setType("group");
			vo2.setMisId(this.getMisId());
			if (cmdMapping.containsKey(vo2.getName())) {
				MvcCmdList.LOG.warn("\u542b\u6709\u91cd\u590d\u7684CmdGroup :" + vo2.getName());
			}
			cmdMapping.put(vo2.getName(), vo2);
		}
		return (List<CmdVo>) Lists.newArrayList((Iterable) cmdMapping.values());
	}

	public void proccessCmd(final Class<?> aClass, final Map<String, CmdVo> cmdMapping,
			final Map<String, String> groupMap, final Map<String, List<CmdVo>> extedsCmd) {
		final RestController rc = aClass.getAnnotation(RestController.class);
		if (rc == null) {
			return;
		}
		MvcCmdList.LOG.debug("ProccessCmd " + aClass.getName());
		final MvcGroup mg = aClass.getAnnotation(MvcGroup.class);
		if (mg != null) {
			final String group = mg.value();
			final String groupName = aClass.getSimpleName();
			final Method[] methods = aClass.getDeclaredMethods();
			for (final Method method : methods) {
				final RequestMapping rm = method.getAnnotation(RequestMapping.class);
				if (rm != null) {
					final MvcDescription md = method.getAnnotation(MvcDescription.class);
					final CmdVo vo = new CmdVo();
					vo.setName(rm.value()[0]);
					vo.setDescription(md.value());
					vo.setGroupName(groupName);
					vo.setGroup(group);
					vo.setCmdClass(aClass);
					vo.setSince(this.getSince());
					vo.setType("cmd");
					vo.setMisId(this.getMisId());
					final Set<String> dependents = new HashSet<String>();
					final Set<String> excludes = new HashSet<String>();
					vo.setIncludes(dependents);
					vo.setExcludes(excludes);
					excludes.add(vo.getName());
					if (mg.includes().length > 0) {
						dependents.addAll(Arrays.asList(mg.includes()));
					}
					if (md.includes().length > 0) {
						dependents.addAll(Arrays.asList(md.includes()));
					}
					if (md.excludes().length > 0) {
						excludes.addAll(Arrays.asList(md.excludes()));
					}
					if (!md.extend().isEmpty()) {
						vo.setExtend(md.extend());
						List<CmdVo> list = extedsCmd.get(md.extend());
						if (list == null) {
							list = Lists.newArrayList();
							extedsCmd.put(md.extend(), list);
						}
						list.add(vo);
					} else {
						dependents.removeAll(excludes);
					}
					groupMap.put(groupName, group);
					if (cmdMapping.containsKey(vo.getName())) {
						MvcCmdList.LOG
								.warn("\u542b\u6709\u91cd\u590d\u7684Cmd :" + vo.getName() + " " + method.getName());
					}
					cmdMapping.put(vo.getName(), vo);
				}
			}
		} else {
			MvcCmdList.LOG.warn(String
					.format("\u626b\u63cfRestController\u6ce8\u89e3\u7684\u7c7b[%s]\uff0c\u4f46\u5b83\u6ca1\u6709MvcGroup\u6ce8\u89e3",
							aClass.getName()));
		}
	}

	public void proccessExtend(final Map<String, CmdVo> cmdMapping, final Map<String, List<CmdVo>> extedsCmd) {
		for (final List<CmdVo> list : extedsCmd.values()) {
			for (final CmdVo vo : list) {
				final CmdVo parent = this.getParentCmdVo(cmdMapping, vo);
				final Set<String> dependents = vo.getIncludes();
				if (parent == null) {
					MvcCmdList.LOG.warn(vo.getName() + " extend [" + vo.getExtend() + "] not exits " + vo.getExtend());
				} else {
					dependents.addAll(parent.getIncludes());
				}
				dependents.removeAll(vo.getExcludes());
			}
		}
	}

	public CmdVo getParentCmdVo(final Map<String, CmdVo> cmdMapping, final CmdVo vo) {
		CmdVo parent = cmdMapping.get(vo.getExtend());
		if (parent == null) {
			return null;
		}
		if (parent.getExtend() == null || parent.getExtend().isEmpty()) {
			return parent;
		}
		final List<String> paths = Lists.newArrayList();
		paths.add(parent.getName());
		while (true) {
			final CmdVo temp = cmdMapping.get(parent.getExtend());
			if (temp == null) {
				MvcCmdList.LOG
						.warn(parent.getName() + " extend [" + parent.getExtend() + "] not exits " + vo.getExtend());
				parent.setExtend("");
			} else {
				parent = temp;
			}
			if (parent.getExtend() == null || parent.getExtend().isEmpty()) {
				for (int i = paths.size() - 1; i >= 0; --i) {
					final String name = paths.get(i);
					final CmdVo child = cmdMapping.get(name);
					final Set<String> dependents = child.getIncludes();
					dependents.addAll(parent.getIncludes());
					dependents.removeAll(child.getExcludes());
					child.setExtend("");
				}
				return parent;
			}
			if (paths.contains(parent.getName())) {
				final String error = "[" + vo.getName() + " <==> " + parent.getName() + "] 含有循环依赖继承!!";
				MvcCmdList.LOG.error(error);
				throw new RuntimeException(error);
			}
			paths.add(parent.getName());
		}
	}

	static {
		LOG = LoggerFactory.getLogger(MvcCmdList.class.getName());
	}
}
